package com.signature.auto.common.util;

import com.signature.auto.common.error.ApiErrorCode;
import com.signature.auto.common.exception.ApiSignException;
import com.signature.auto.common.servlet.ApiRequest;

import java.util.Map;
import java.util.TreeMap;

/**
 * @author Jerry
 * @date 2024-01-24 14:12
 * 签名工具
 */
public abstract class SignUtil {


    // ----------- 拼接参数

    /**
     * 将所有参数连接成一个字符串(不排序)，形如：b=28a=18c=3
     *
     * @param paramsMap 参数列表
     * @return 拼接出的参数字符串
     */
    public String joinParams(Map<String, ?> paramsMap) {

        // 按照 k1=v1&k2=v2&k3=v3 排列
        StringBuilder sb = new StringBuilder();
        for (String key : paramsMap.keySet()) {
            Object value = paramsMap.get(key);
            if (!ApiFoxUtil.isEmpty(value)) {
                sb.append(key).append("=").append(value).append("&");
            }
        }
        // 删除最后一位 &
        if (!sb.isEmpty()) {
            sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }

    /**
     * 将所有参数按照字典顺序连接成一个字符串，形如：a=18b=28c=3
     *
     * @param paramsMap 参数列表
     * @return 拼接出的参数字符串
     */
    public String joinParamsDictSort(Map<String, ?> paramsMap) {
        // 保证字段按照字典顺序排列
        if (!(paramsMap instanceof TreeMap)) {
            paramsMap = new TreeMap<>(paramsMap);
        }
        // 拼接
        return joinParams(paramsMap);
    }

    // ----------- 创建签名

    /**
     * 创建签名：md5(paramsStr + keyStr)
     *
     * @param paramsMap 参数列表
     * @return 签名
     */
    public String createSign(Map<String, ?> paramsMap, String secretKey) {
        ApiSignException.throwByNull(secretKey, "参与参数签名的秘钥不可为空", ApiErrorCode.CODE_12201);
        // 如果调用者不小心传入了 sign 参数，则此处需要将 sign 参数排除在外
        if (paramsMap.containsKey("sign")) {
            // 为了保证不影响原有的 paramsMap，此处需要再复制一份
            paramsMap = new TreeMap<>(paramsMap);
            paramsMap.remove("sign");
        }
        // 计算签名
        String paramsStr = joinParamsDictSort(paramsMap);
        String fullStr = paramsStr + "&" + "key" + "=" + secretKey;
        return abstractStr(fullStr);
    }

    /**
     * 使用摘要算法创建签名
     *
     * @param fullStr 待摘要的字符串
     * @return 签名
     */
    public abstract String abstractStr(String fullStr);
//    {
//        return SecureUtil.md5(fullStr);
//    }

    /**
     * 给 paramsMap 追加 timestamp、nonce、sign 三个参数
     *
     * @param paramsMap 参数列表
     * @return 加工后的参数列表
     */
    public abstract Map<String, Object> addSignParams(Map<String, Object> paramsMap, String secretKey);
//    {
//        paramsMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
//        paramsMap.put("nonce", ApiFoxUtil.getRandomString(32));
//        paramsMap.put("sign", createSign(paramsMap, secretKey));
//        return paramsMap;
//    }

    /**
     * 给 paramsMap 追加 timestamp、nonce、sign 三个参数，并转换为参数字符串，形如：
     * <code>data=xxx8nonce=xxx8timestamp=xxx8sign=xxx</code>
     *
     * @param paramsMap 参数列表
     * @return 加工后的参数列表 转化为的参数字符串
     */
    public String addSignParamsAndJoin(Map<String, Object> paramsMap, String secretKey) {
        // 追加参数
        paramsMap = addSignParams(paramsMap, secretKey);
        // 拼接参数
        return joinParams(paramsMap);
    }

    // ----------- 校验签名

    /**
     * 判断：指定时间戳与当前时间戳的差距是否在允许的范围内
     *
     * @param timestamp 待校验的时间戳
     * @return 是否在允许的范围内
     */
    public abstract boolean isValidTimestamp(long timestamp);

    /**
     * 校验：指定时间戳与当前时间戳的差距是否在允许的范围内，如果超出则抛出异常
     *
     * @param timestamp 待校验的时间戳
     */
    public void checkTimestamp(long timestamp) {
        if (!isValidTimestamp(timestamp)) {
            throw new ApiSignException("timestamp 超出允许的范围：" + timestamp).setCode(ApiErrorCode.CODE_12203);
        }
    }

    /**
     * 判断：随机字符串 nonce 是否有效。
     * 注意：同一 nonce 可以被多次判断有效，不会被缓存
     *
     * @param nonce 待判断的随机字符串
     * @return 是否有效
     */
    public boolean isValidNonce(String nonce) {
        // 为空代表无效
        if (ApiFoxUtil.isEmpty(nonce)) {
            return false;
        }

        // 校验此 nonce 是否已被使用过
        String key = splicingNonceSaveKey(nonce);
        // TODO 随机字符串校验
        return true;
//        return ApiSignContext.getApiSignDao().get(key) == null;
    }

    /**
     * 校验：随机字符串 nonce 是否有效，如果无效则抛出异常。
     * 注意：同一 nonce 只可以被校验通过一次，校验后将保存在缓存中，再次校验将无法通过
     *
     * @param nonce 待校验的随机字符串
     */
    public void checkNonce(String nonce) {
        // 为空代表无效
        if (ApiFoxUtil.isEmpty(nonce)) {
            throw new ApiSignException("nonce 为空，无效");
        }

        // 校验此 nonce 是否已被使用过
        String key = splicingNonceSaveKey(nonce);
        // TODO 随机字符串校验
//        if (ApiSignContext.getApiSignDao().get(key) != null) {
//            throw new ApiSignException("此 nonce 已被使用过，不可重复使用：" + nonce);
//        }
        // 校验通过后，将此 nonce 保存在缓存中，保证下次校验无法通过
//        ApiSignContext.getApiSignDao().set(key, nonce, getSignConfigOrGlobal().getSaveNonceExpire() * 2 + 2);
    }

    /**
     * 判断：给定的参数 + 秘钥 生成的签名是否为有效签名
     *
     * @param paramsMap 参数列表
     * @param sign      待验证的签名
     * @param secretKey 秘钥
     * @return 签名是否有效
     */
    public boolean isValidSign(Map<String, ?> paramsMap, String sign, String secretKey) {
        String theSign = createSign(paramsMap, secretKey);
        System.out.println("签名结果：======> " + theSign);
        return theSign.equals(sign);
    }

    /**
     * 校验：给定的参数 + 秘钥 生成的签名是否为有效签名，如果签名无效则抛出异常
     *
     * @param paramsMap 参数列表
     * @param sign      待验证的签名
     * @param secretKey 秘钥
     */
    public void checkSign(Map<String, ?> paramsMap, String sign, String secretKey) {
        if (!isValidSign(paramsMap, sign, secretKey)) {
            throw new ApiSignException("无效签名：" + sign).setCode(ApiErrorCode.CODE_12202);
        }
    }

    /**
     * 判断：参数列表中的 nonce、timestamp、sign 是否均为合法的
     *
     * @param paramMap 待校验的请求参数集合
     * @return 是否合法
     */
    @SuppressWarnings("all")
    public abstract boolean isValidParamMap(Map<String, Object> paramMap, String secretKey);

    /**
     * 校验：参数列表中的 nonce、timestamp、sign 是否均为合法的，如果不合法，则抛出对应的异常
     *
     * @param paramMap 待校验的请求参数集合
     */
    public abstract void checkParamMap(Map<String, Object> paramMap, String secretKey);

    /**
     * 判断：一个请求中的 nonce、timestamp、sign 是否均为合法的
     *
     * @param request 待校验的请求对象
     * @return 是否合法
     */
    public boolean isValidRequest(ApiRequest request, String secretKey) {
        return isValidParamMap(request.getParamMap(), secretKey);
    }

    /**
     * 校验：一个请求的 nonce、timestamp、sign 是否均为合法的，如果不合法，则抛出对应的异常
     *
     * @param request 待校验的请求对象
     */
    public void checkRequest(ApiRequest request, String secretKey) {
        Map<String, Object> paramMap = request.getParamMap();
        checkParamMap(paramMap, secretKey);
    }


    // ------------------- 返回相应key -------------------

    /**
     * 拼接key：存储 nonce 时使用的 key
     *
     * @param nonce nonce 值
     * @return key
     */
    public String splicingNonceSaveKey(String nonce) {
        // todo
        return "";
//        return SaManager.getConfig().getTokenName() + ":sign:nonce:" + nonce;
    }


}
